/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 * @see org.springframework.security.annotation.SecurityAnnotationAttributes
 **/
package com.genia.toolbox.security.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.springframework.metadata.Attributes;
import org.springframework.security.SecurityConfig;
import org.springframework.security.annotation.Secured;
import org.springframework.util.ClassUtils;

/**
 * Java 5 Annotation <code>Attributes</code> metadata implementation used for
 * secure method interception.
 * <p>
 * This works like
 * {@link org.springframework.security.annotation.SecurityAnnotationAttributes} but allows
 * to distinguish beetween multiple annotations.
 * 
 * @see AffirmativeSecured
 * @see ConsensusSecured
 * @see UnanimousSecured
 */
public class SecurityAnnotationAttributes
    implements Attributes
{

  /**
   * the marker classes.
   */
  @SuppressWarnings( { "unchecked" })
  private static final Set<Class<? extends Annotation>> MARKER_CLASSES = new HashSet<Class<? extends Annotation>>(Arrays.asList(NeedAllRole.class, NeedAnyRole.class));

  /**
   * the class of the annotation to consider.
   */
  private Class<?> annotationClass;

  /**
   * whether to accept a {@link Secured} annotated element without qualifier.
   */
  private boolean fallback = false;



  // ~ Methods
  // ========================================================================================================

  /**
   * Get the <code>Secured</code> attributes for a given target class.
   * 
   * @param target
   *          The target method
   * @return Collection of <code>SecurityConfig</code>
   * @see Attributes#getAttributes
   */

  public Set<SecurityConfig> getAttributes(@SuppressWarnings("unchecked")
  Class target)
  {
    return getAttributes(target.getAnnotations());
  }



  /**
   * Unimplemented method.
   * 
   * @param targetClass
   *          the class that contains attribute information
   * @param filter
   *          specify that only this type of class should be returned
   * @return return only the Collection of attributes that are of the filter
   *         type
   */
  public Set<SecurityConfig> getAttributes(@SuppressWarnings("unchecked")
  Class targetClass, @SuppressWarnings("unchecked")
  Class filter)
  {
    throw new UnsupportedOperationException("Unsupported operation");
  }



  /**
   * return the set of {@link SecurityConfig} defined by the given annotations.
   * 
   * @param annotations
   *          the annotation to parse
   * @return the set of {@link SecurityConfig} defined by the given annotations
   */
  private Set<SecurityConfig> getAttributes(Annotation[] annotations)
  {
    Set<SecurityConfig> attributes = new HashSet<SecurityConfig>();
    boolean hasMarker = false;
    boolean isMarked = (getAnnotationClass() == null);
    for (Annotation annotation : annotations) {
      // Check whether the annotation is one of the marker annotation.
      hasMarker = hasMarker || MARKER_CLASSES.contains(hasMarker);
      // check for marker annotation
      isMarked = isMarked || getAnnotationClass().isAssignableFrom(annotation.getClass());
      // check for Secured annotations
      if (annotation instanceof Secured) {
        for (String auth : ((Secured) annotation).value()) {
          attributes.add(new SecurityConfig(auth));
        }
      }
    }
    if (!isMarked) {
      if (hasMarker || !isFallback()) {
        attributes.clear();
      }
    }
    return attributes;
  }



  /**
   * Get the <code>Secured</code> attributes for a given target method.
   * 
   * @param method
   *          The target method
   * @return Collection of <code>SecurityConfig</code>
   * @see Attributes#getAttributes
   */
  public Set<SecurityConfig> getAttributes(Method method)
  {

    Annotation[] annotations = null;

    // Use AnnotationUtils if in classpath (ie Spring 1.2.9+ deployment)
    try {
      Class<?> clazz = ClassUtils.forName("org.springframework.core.annotation.AnnotationUtils");
      Method m = clazz.getMethod("getAnnotations", new Class[] { Method.class });
      annotations = (Annotation[]) m.invoke(null, new Object[] { method });
    }
    catch (Exception ex) {
      // Fallback to manual retrieval if no AnnotationUtils available
      annotations = method.getAnnotations();
    }
    return getAttributes(annotations);
  }



  /**
   * Unimplemented method.
   * 
   * @param targetMethod
   *          the method that contains attribute information
   * @param filter
   *          specify that only this type of class should be returned
   * @return a Collection of attributes, possibly an empty Collection, never
   *         <code>null</code>
   */
  public Collection<?> getAttributes(Method targetMethod, @SuppressWarnings("unchecked")
  Class filter)
  {
    throw new UnsupportedOperationException("Unsupported operation");
  }



  /**
   * Unimplemented method.
   * 
   * @param targetField
   *          the field that contains attribute information
   * @return a Collection of attribute, possibly an empty Collection, never
   *         <code>null</code>
   */
  public Collection<?> getAttributes(Field targetField)
  {
    throw new UnsupportedOperationException("Unsupported operation");
  }



  /**
   * Unimplemented method.
   * 
   * @param targetField
   *          the field that contains attribute information
   * @param filter
   *          specify that only this type of class should be returned
   * @return a Collection of attributes, possibly an empty Collection, never
   *         <code>null</code>
   */
  public Collection<?> getAttributes(Field targetField, @SuppressWarnings("unchecked")
  Class filter)
  {
    throw new UnsupportedOperationException("Unsupported operation");
  }



  /**
   * getter for the annotationClass property.
   * 
   * @return the annotationClass
   */
  public Class<?> getAnnotationClass()
  {
    return annotationClass;
  }



  /**
   * setter for the annotationClass property.
   * 
   * @param annotationClass
   *          the annotationClass to set
   */
  public void setAnnotationClass(Class<?> annotationClass)
  {
    this.annotationClass = annotationClass;
  }



  /**
   * getter for the fallback property.
   * 
   * @return the fallback
   */
  public boolean isFallback()
  {
    return fallback;
  }



  /**
   * setter for the fallback property.
   * 
   * @param fallback
   *          the fallback to set
   */
  public void setFallback(boolean fallback)
  {
    this.fallback = fallback;
  }
}
